﻿using System;
using System.Collections.Generic;
using System.Diagnostics;
using System.Linq;
using System.Text;
using System.Windows;
using System.Windows.Controls;
using System.Windows.Media;
using System.Windows.Media.Imaging;
using EnvDTE;
using EnvDTE80;

namespace CodeJumper
{
    public class CodeElementAggregate
    {
        private readonly ApplicationConfiguration _appConfig;
        private readonly string _name;
        private bool _visualsInitialized = false;
        private ElementStackPanel _elementStackPanel;

        public List<CodeElementAggregate> Children = new List<CodeElementAggregate>();
        public ElementInfo ElementInfo = new ElementInfo();
        public ElementLabel ElementLabel;
        public CodeElementType ElementType;
        public string FullName;
        public bool IsRoot = false;
        public CodeElement Original;
        public CodeElementAggregate Parent;
        public string UniqueId;


        public CodeElementAggregate(ApplicationConfiguration AppConfig)
        {
            this._appConfig = AppConfig;
            Parent = null; // ROOT
            IsRoot = true;
            FullName = "CODE_JUMPER_MONKEY X";
            UniqueId = "CODE_JUMPER_MONKEY X";

            ElementType = CodeElementType.Root;
        }

        public CodeElementAggregate(ApplicationConfiguration AppConfig, CodeElement Original, CodeElementAggregate Parent)
        {
            this._appConfig = AppConfig;
            this.Original = Original;
            this.Parent = Parent;
            FullName = Original.FullName;
            _name = Original.Name;

            UniqueId = CodeElementHelper.GetUniqueElementId(Original);

            ElementType = CodeElementHelper.GetElementType(Original);
            ElementInfo.ElementType = ElementType;

            ParseCodeElement();
        }

        public bool IsStackableElement
        {
            get
            {
                if (IsRoot)
                    return true;
                return (ElementType == CodeElementType.Class || ElementType == CodeElementType.Namespace
                        || ElementType == CodeElementType.Enum || ElementType == CodeElementType.Interface
                        || ElementType == CodeElementType.Structure);
            }
        }

        private void ParseCodeElement()
        {
            #region NAMESPACE

            if (ElementInfo.ElementType == CodeElementType.Namespace)
            {
                ElementInfo.IsStackable = true;
                var codeNamespace = (CodeNamespace) Original;
            }

            #endregion

            #region CLASS

            if (ElementInfo.ElementType == CodeElementType.Class)
            {
                ElementInfo.IsStackable = true;
                var codeClass = (CodeClass) Original;
                var codeClass2 = (CodeClass2) Original;
                ElementInfo.Accessibility = new AccessibilityContainer(_appConfig, codeClass.Access);
                ElementInfo.IsStatic = codeClass2.IsShared == false;
            }

            #endregion

            #region INTERFACE

            if (ElementInfo.ElementType == CodeElementType.Interface)
            {
                ElementInfo.IsStackable = true;
                var inf = (CodeInterface) Original;
                var inf2 = (CodeInterface2) Original;
                ElementInfo.Accessibility = new AccessibilityContainer(_appConfig, inf.Access);
            }

            #endregion

            #region STRUCTURE

            if (ElementInfo.ElementType == CodeElementType.Structure)
            {
                ElementInfo.IsStackable = true;
                var struc = (CodeStruct) Original;
                ElementInfo.Accessibility = new AccessibilityContainer(_appConfig, struc.Access);
            }

            #endregion

            #region METHOD

            if (ElementInfo.ElementType == CodeElementType.Method)
            {
                var func = (CodeFunction) Original;
                var func2 = (CodeFunction2) Original;

                ElementInfo.IsStatic = func.IsShared == false;
                ElementInfo.IsConstructor = func.FunctionKind == vsCMFunction.vsCMFunctionConstructor;
                ElementInfo.IsVirtual = func.FunctionKind == vsCMFunction.vsCMFunctionVirtual;
                // TODO: Extension methods how?

                ElementInfo.Accessibility = new AccessibilityContainer(_appConfig, func.Access);


                var builder = new StringBuilder();
                int count = 1;
                string separator = ", ";
                foreach (object parameter in func.Parameters)
                {
                    if (count == func.Parameters.Count)
                        separator = "";
                    var param = parameter as CodeParameter;
                    string paramTypeName = param.Type.AsString;
                    int dotPos = paramTypeName.LastIndexOf('.');
                    if (dotPos != -1)
                        paramTypeName = paramTypeName.Substring(dotPos + 1);
                    builder.AppendFormat("{0}{1}", paramTypeName, separator);
                    count++;
                }

                ElementInfo.ParameterString = builder.ToString();
            }

            #endregion

            #region PROPERTY

            if (ElementInfo.ElementType == CodeElementType.Property)
            {
                var prop = (CodeProperty) Original;
                var prop2 = (CodeProperty2) Original;
                ElementInfo.IsStatic = prop2.IsShared == false;

                ElementInfo.Accessibility = new AccessibilityContainer(_appConfig, prop.Access);
            }

            #endregion

            #region VARIABLE

            if (ElementInfo.ElementType == CodeElementType.Variable)
            {
                var vari = (CodeVariable) Original;
                var vari2 = (CodeVariable2) Original;

                ElementInfo.IsConstant = vari2.ConstKind == vsCMConstKind.vsCMConstKindConst;
                ElementInfo.IsReadOnly = vari2.ConstKind == vsCMConstKind.vsCMConstKindReadOnly;
                ElementInfo.IsStatic = vari2.IsShared == false;


                if (vari.Parent != null)
                {
                    if ((vari.Parent as CodeElement).Kind == vsCMElement.vsCMElementEnum)
                    {
                        ElementInfo.IsEnumValue = true;
                    }
                }

                ElementInfo.Accessibility = new AccessibilityContainer(_appConfig, vari.Access);
            }

            #endregion

            #region ENUM

            if (ElementInfo.ElementType == CodeElementType.Enum)
            {
                ElementInfo.IsStackable = true;
                var enu = (CodeEnum) Original;
                ElementInfo.Accessibility = new AccessibilityContainer(_appConfig, enu.Access);
            }

            #endregion

            #region DELEGATE

            if (ElementInfo.ElementType == CodeElementType.Delegate)
            {
                var dele = (CodeDelegate) Original;
                ElementInfo.Accessibility = new AccessibilityContainer(_appConfig, dele.Access);
            }

            #endregion

            #region EVENT

            if (ElementInfo.ElementType == CodeElementType.Event)
            {
                var eve = (CodeEvent) Original;
                ElementInfo.IsStatic = eve.IsShared == false;
                ElementInfo.Accessibility = new AccessibilityContainer(_appConfig, eve.Access);
            }

            #endregion
        }


        public void Draw()
        {
            if (ElementType == CodeElementType.Root)
            {
                _elementStackPanel = new ElementStackPanel(_appConfig);
                _elementStackPanel.Orientation = Orientation.Vertical;
                _elementStackPanel.Background = _appConfig.ViewPortBackgroundBrush;
                _elementStackPanel.Margin = new Thickness(0, 0, 0, 0);
                _elementStackPanel.ShowBorder = false;
            }
            else
            {
                bool result = GetElementInfo();
                if (result == false)
                    return;
                _elementStackPanel = new ElementStackPanel(_appConfig);
                _elementStackPanel.Orientation = Orientation.Vertical;
                _elementStackPanel.Background = Brushes.Green;
                _elementStackPanel.Margin = new Thickness(2);
                if (ElementType == CodeElementType.Class)
                {
                    _elementStackPanel.Background = _appConfig.ClassBackgroundBrush;
                }
                if (ElementType == CodeElementType.Interface)
                {
                    _elementStackPanel.Background = _appConfig.InterfaceBackgroundBrush;
                }
                if (ElementType == CodeElementType.Enum)
                {
                    _elementStackPanel.Background = _appConfig.EnumBackgroundBrush;
                }
                if (ElementType == CodeElementType.Structure)
                {
                    _elementStackPanel.Background = _appConfig.StructBackgroundBrush;
                }
                if (ElementType == CodeElementType.Namespace)
                {
                    _elementStackPanel.Background = _appConfig.NamspaceBackgroundBrush;
                }
                ElementLabel = new ElementLabel(_appConfig, this);

                // When user clicks on item in code map
                ElementLabel.Click += (s, e) => JumpToSource();
            }

            DrawChildren();
        }

        public void DrawChildren()
        {
            foreach (CodeElementAggregate child in Children)
            {
                child.Draw();
                _elementStackPanel.Children.Add(child.GetVisualElement());

                if (child.IsStackableElement)
                {
                    (child.GetVisualElement() as ElementStackPanel).SetHeader(child.ElementLabel);
                }
            }
        }

        public void JumpToSource(vsCMPart location = vsCMPart.vsCMPartNavigate)
        {
            TextPoint startPoint = Original.GetStartPoint(location);
            Document openDoc = _appConfig.HookInformation.Dte.ActiveDocument.ProjectItem.Document;
            ((TextSelection) openDoc.Selection).MoveToPoint(startPoint);
            startPoint.TryToShow();
            openDoc.Activate();
        }

        public void SortChildrenAlphabetically()
        {
            if (Children.Count == 0)
                return;

            Children.Sort((x, y) => System.String.CompareOrdinal(x.Original.Name, y.Original.Name));
            foreach (CodeElementAggregate child in Children)
            {
                child.SortChildrenAlphabetically();
            }
        }

        public CodeElementAggregate Find(CodeElement element)
        {
            if (element == null)
                return null;
            if (IsRoot == false)
            {
                if (UniqueId == CodeElementHelper.GetUniqueElementId(element))
                    return this;
            }

            return Children.Select(child => child.Find(element)).FirstOrDefault(foundChild => foundChild != null);
        }

        public CodeElementAggregate TraverseChildrenForFirstThrow(CodeElementAggregate aggregate)
        {
            if (!aggregate.IsRoot)
            {
                try
                {
                    Trace.WriteLine(string.Format("Checking for rename: {0}", aggregate.ElementInfo.Name));
                    string name = (aggregate.Original as CodeType).Name;
                }
                catch
                {
                    return aggregate;
                }
            }
            foreach (var child in aggregate.Children)
            {
                var aggr = TraverseChildrenForFirstThrow(child);
                if (aggr != null)
                    return aggr;
            }
            return null;
        }

        private CodeElementAggregate AddChild(CodeElement Original)
        {
            var child = new CodeElementAggregate(_appConfig, Original, this);
            //child.Update();
            Children.Add(child);


            return child;
        }

        public void Clear()
        {
            foreach (CodeElementAggregate child in Children)
            {
                child.Clear();
            }

            Children.Clear();
        }

        public void UpdateAll()
        {
            Update();
            UpdateChildren();
        }

        public void UpdateChildren()
        {
            foreach (CodeElementAggregate child in Children)
            {
                child.UpdateAll();
            }
        }

        public CodeElementAggregate Add(CodeElement element)
        {
            CodeElement parent = CodeElementHelper.GetParent(element);


            CodeElementAggregate parentaggr = Find(parent);
            if (parentaggr == null)
            {
                if (IsRoot)
                {
                    parentaggr = this;
                }
                else
                {
                    Trace.WriteLine("Unable to find parent aggregate.");
                    return null;
                }
            }

            CodeElementAggregate child = parentaggr.AddChild(element);


            //  parentaggr.Update();
            return child;
        }

        public void Delete(CodeElement parent, CodeElement Element)
        {
            CodeElementAggregate parentaggr = Find(parent);

            // We have to do this as the Element is already destructed and the only thing with have is the Parent path.Name
            string uid = string.Format("{0}.{1}", parent.FullName, Element.Name);

            foreach (CodeElementAggregate child in parentaggr.Children)
            {
                if (child.UniqueId != uid) continue;
                parentaggr.Children.Remove(child);

                parentaggr._elementStackPanel.Children.Remove(child.GetVisualElement());
                return;
            }
            Trace.WriteLine("NOT FOUND!!!");
        }


        public void Update()
        {
            if (Original == null)
                return;

            ElementLabel.Clear();

            if (ElementInfo.Accessibility != null)
            {
                ElementInfo.Accessibility.Create();

                ElementLabel.Opacity = ElementInfo.Accessibility.Opacity;
            }

            if (ElementType == CodeElementType.Namespace)
            {
                ElementLabel.AddExpander(_elementStackPanel);
                ElementLabel.AddPictureToSingleGrid(ElementInfo.ElementImage);
                ElementLabel.AddPictureToSingleGrid(ElementInfo.Accessibility.AccessibilityImage);

                ElementLabel.AddText(ElementInfo.Name, _appConfig.NamespaceTextBrush, _appConfig.HyperlinkTextImageSpacer, FontWeights.Bold);
            }

            if (ElementType == CodeElementType.Class)
            {
                ElementLabel.AddExpander(_elementStackPanel);
                ElementLabel.AddPictureToSingleGrid(ElementInfo.ElementImage);
                ElementLabel.AddPictureToSingleGrid(ElementInfo.Accessibility.AccessibilityImage);

                ElementLabel.AddText(ElementInfo.Name, _appConfig.ClassTextBrush, _appConfig.HyperlinkTextImageSpacer, FontWeights.Bold);
            }

            if (ElementType == CodeElementType.Interface)
            {
                ElementLabel.AddExpander(_elementStackPanel);
                ElementLabel.AddPictureToSingleGrid(ElementInfo.ElementImage);
                ElementLabel.AddPictureToSingleGrid(ElementInfo.Accessibility.AccessibilityImage);

                ElementLabel.AddText(ElementInfo.Name, _appConfig.ClassTextBrush, _appConfig.HyperlinkTextImageSpacer, FontWeights.Bold);
            }

            if (ElementType == CodeElementType.Enum)
            {
                ElementLabel.AddExpander(_elementStackPanel, false);
                ElementLabel.AddPictureToSingleGrid(ElementInfo.ElementImage);
                ElementLabel.AddPictureToSingleGrid(ElementInfo.Accessibility.AccessibilityImage);

                ElementLabel.AddText(ElementInfo.Name, _appConfig.EnumTextBrush, _appConfig.HyperlinkTextImageSpacer, FontWeights.Bold);
            }
            if (ElementType == CodeElementType.Structure)
            {
                ElementLabel.AddExpander(_elementStackPanel, false);
                ElementLabel.AddPictureToSingleGrid(ElementInfo.ElementImage);
                ElementLabel.AddPictureToSingleGrid(ElementInfo.Accessibility.AccessibilityImage);

                ElementLabel.AddText(ElementInfo.Name, _appConfig.EnumTextBrush, _appConfig.HyperlinkTextImageSpacer, FontWeights.Bold);
            }

            if (ElementType == CodeElementType.Property)
            {
                if (_appConfig.HideProperties == false)
                    ElementLabel.Visibility = Visibility.Visible;
                else
                    ElementLabel.Visibility = Visibility.Collapsed;

                ElementLabel.AddPictureToSingleGrid(ElementInfo.ElementImage);
                ElementLabel.AddPictureToSingleGrid(ElementInfo.Accessibility.AccessibilityImage);

                ElementLabel.AddText(ElementInfo.Name, _appConfig.PropertyTextBrush, _appConfig.HyperlinkTextImageSpacer, FontWeights.Normal);
            }

            if (ElementType == CodeElementType.Variable)
            {
                // Check whether this variable is not part of class, but rather an enum or struct
                if (_appConfig.HideVariables == false || ElementInfo.IsStackable)
                    ElementLabel.Visibility = Visibility.Visible;
                else
                    ElementLabel.Visibility = Visibility.Collapsed;

                ElementLabel.AddPictureToSingleGrid(ElementInfo.ElementImage);
                ElementLabel.AddPictureToSingleGrid(ElementInfo.Accessibility.AccessibilityImage);
                ElementLabel.AddText(ElementInfo.Name, _appConfig.VariableTextBrush, _appConfig.HyperlinkTextImageSpacer, FontWeights.Normal);
            }

            if (ElementType == CodeElementType.Delegate)
            {
                ElementLabel.AddPictureToSingleGrid(ElementInfo.ElementImage);
                ElementLabel.AddPictureToSingleGrid(ElementInfo.Accessibility.AccessibilityImage);

                ElementLabel.AddText(ElementInfo.Name, _appConfig.DelegateTextBrush, _appConfig.HyperlinkTextImageSpacer, FontWeights.Normal);
            }

            if (ElementType == CodeElementType.Event)
            {
                ElementLabel.AddPictureToSingleGrid(ElementInfo.ElementImage);
                ElementLabel.AddPictureToSingleGrid(ElementInfo.Accessibility.AccessibilityImage);

                ElementLabel.AddText(ElementInfo.Name, _appConfig.EventTextBrush, _appConfig.HyperlinkTextImageSpacer, FontWeights.Normal);
            }

            if (ElementType == CodeElementType.Method)
            {
                ElementLabel.AddPictureToSingleGrid(ElementInfo.ElementImage);
                ElementLabel.AddPictureToSingleGrid(ElementInfo.Accessibility.AccessibilityImage);
                ElementLabel.AddText(ElementInfo.Name, _appConfig.MethodTextBrush, _appConfig.HyperlinkTextImageSpacer, FontWeights.Normal);
                ElementLabel.AddText(string.Format("  ( {0} )", ElementInfo.ParameterString), _appConfig.MethodParametersTextBrush);
            }
        }

        private BitmapImage GetAccessorImage(vsCMAccess access)
        {
            switch (access)
            {
                case vsCMAccess.vsCMAccessPrivate:
                    {
                        return _appConfig.GetImage("private");
                    }
                case vsCMAccess.vsCMAccessProtected:
                    {
                        return _appConfig.GetImage("protected");
                    }
                case vsCMAccess.vsCMAccessProject:
                    {
                        return _appConfig.GetImage("internal");
                    }
                case vsCMAccess.vsCMAccessPublic:
                    {
                        return _appConfig.GetImage("public");
                    }
                case vsCMAccess.vsCMAccessProjectOrProtected:
                    {
                        return _appConfig.GetImage("protectedinternal");
                    }
            }

            return null;
        }

        private bool GetElementInfo()
        {
            try
            {
                ElementInfo.ElementType = ElementType;
                ElementInfo.Name = _name;

                #region NAMESPACE

                if (ElementInfo.ElementType == CodeElementType.Namespace)
                {
                    ElementInfo.ElementImage = _appConfig.GetImage("namespace");
                }

                #endregion

                #region CLASS

                if (ElementInfo.ElementType == CodeElementType.Class)
                {
                    if (ElementInfo.IsStatic)
                    {
                        ElementInfo.ElementImage = _appConfig.GetImage("staticclass");
                    }
                    else
                        ElementInfo.ElementImage = _appConfig.GetImage("class");
                }

                #endregion

                #region INTERFACE

                if (ElementInfo.ElementType == CodeElementType.Interface)
                {
                    ElementInfo.ElementImage = _appConfig.GetImage("interface");
                }

                #endregion

                #region STRUCTURE

                if (ElementInfo.ElementType == CodeElementType.Structure)
                {
                    ElementInfo.ElementImage = _appConfig.GetImage("struct");
                }

                #endregion

                #region METHOD

                if (ElementInfo.ElementType == CodeElementType.Method)
                {
                    if (ElementInfo.IsVirtual)
                    {
                        ElementInfo.ElementImage = _appConfig.GetImage("virtualmethod");
                    }
                    else if (ElementInfo.IsConstructor)
                    {
                        ElementInfo.ElementImage = _appConfig.GetImage("constructor");
                    }
                    else
                        ElementInfo.ElementImage = _appConfig.GetImage("method");
                }

                #endregion

                #region PROPERTY

                if (ElementInfo.ElementType == CodeElementType.Property)
                {
                    ElementInfo.ElementImage = _appConfig.GetImage("property");
                }

                #endregion

                #region VARIABLE

                if (ElementInfo.ElementType == CodeElementType.Variable)
                {
                    if (ElementInfo.IsEnumValue)
                        ElementInfo.ElementImage = _appConfig.GetImage("enumvalue");
                    else if (ElementInfo.IsConstant)
                        ElementInfo.ElementImage = _appConfig.GetImage("constant");
                    else if (ElementInfo.IsReadOnly)
                        ElementInfo.ElementImage = _appConfig.GetImage("fieldreadonly");
                    else
                        ElementInfo.ElementImage = _appConfig.GetImage("field");
                }

                #endregion

                #region ENUM

                if (ElementInfo.ElementType == CodeElementType.Enum)
                {
                    ElementInfo.ElementImage = _appConfig.GetImage("enum");
                }

                #endregion

                #region DELEGATE

                if (ElementInfo.ElementType == CodeElementType.Delegate)
                {
                    ElementInfo.ElementImage = _appConfig.GetImage("delegate");
                }

                #endregion

                #region EVENT

                if (ElementInfo.ElementType == CodeElementType.Event)
                {
                    ElementInfo.ElementImage = _appConfig.GetImage("event");
                }

                #endregion

                return true;
            }
            catch (Exception e)
            {
                Trace.WriteLine(string.Format("Exception in GetElementInfo: {0}", e));
                return false;
            }
        }

        public UIElement GetVisualElement()
        {
            if (IsStackableElement)
            {
                return _elementStackPanel;
            }
            else
                return ElementLabel;
        }
    }
}